﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Upreader.Usenet.Nntp.Encodings
{
    public class UUEncodedStream : StreamWrapper, IEncodedStreamValidatable
    {
        #region Fields

        private bool _isFirstLineDetected;
        private byte _currentLineCharactersLeft;
        private bool _isCurrentLineCharactersLeftSet;
        
        private readonly byte[] _decodeBuffer;
        private int _decodeBufferIndex;

        private bool? _isValid;

        #endregion // Fields

        #region Constructor

        public UUEncodedStream(Stream baseStream)
            : base(baseStream)
        {
            _decodeBuffer = new byte[4];
        }

        #endregion // Constructor

        #region Methods

        public override int Read(byte[] buffer, int offset, int count)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");
            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset");
            if (count < 0)
                throw new ArgumentOutOfRangeException("count");
            if (count > buffer.Length - offset)
                throw new ArgumentOutOfRangeException("count", "to many bytes were requested for the current buffer when starting from offset");

            if (!_isFirstLineDetected)
            {
                DetectFirstLine();
            }

            // read in the next part of the buffer using the input buffer itself
            int total = base.Read(buffer, offset, count);
            int actual = 0;

            for (int index = 0; index < total; index++)
            {
                byte originalByte = buffer[offset + index];
                if (originalByte == 13)
                {
                    // do nothing, since this will be followed up by an endline
                }
                else if (originalByte == 10)
                {   
                    // if there are remaining bytes expected on this line, this line can be marked as broken since we clearly dont have any
                    if (_currentLineCharactersLeft != 0)
                    {
                        _currentLineCharactersLeft = 0;
                        _isValid = false;
                    }
                    
                    // and reset so we will be ready to catch the next line
                    _isCurrentLineCharactersLeftSet = false;
                }
                else if (originalByte == 0x60) // meaning a `
                {
                    // this means the end of the packet is near (at least all the data has been red).
                    // if there are remaining bytes expected on this line, this line can be marked as broken since we clearly dont have any
                    _isValid = _currentLineCharactersLeft == 0;

                    // read the remaining bytes since they will mess up the output otherwise
                    int remaining = total - index;
                    if (remaining < 5) // 5 stands for the amount of bytes that are expected to come after the current byte '`\r\nend';
                    {
                        byte[] tempBuffer = new byte[5 - remaining];
                        // Read the remaining bytes and in a new temp buffer
                        BaseStream.Read(_decodeBuffer, 0, tempBuffer.Length);
                    }
                }
                else
                {
                    byte decodedByte = (byte)(originalByte - 32);

                    if (!_isCurrentLineCharactersLeftSet)
                    {
                        // total amount of resulting characters in the output produced by this line
                        _currentLineCharactersLeft = decodedByte;
                        _isCurrentLineCharactersLeftSet = true;
                    }
                    else
                    {
                        _decodeBuffer[_decodeBufferIndex] = decodedByte;
                        _decodeBufferIndex++;

                        if (_decodeBufferIndex == _decodeBuffer.Length)
                        {
                            // do the actual decoding
                            if (_currentLineCharactersLeft > 0)
                            {
                                buffer[actual++] = (byte)(_decodeBuffer[0] << 2 | _decodeBuffer[1] >> 4);
                                _currentLineCharactersLeft--;
                            }
                            if (_currentLineCharactersLeft > 0)
                            {
                                buffer[actual++] = (byte)(_decodeBuffer[1] << 4 | _decodeBuffer[2] >> 2);
                                _currentLineCharactersLeft--;
                            }
                            if (_currentLineCharactersLeft > 0)
                            {
                                buffer[actual++] = (byte)(_decodeBuffer[2] << 6 | _decodeBuffer[3] >> 0);
                                _currentLineCharactersLeft--;
                            }

                            _decodeBufferIndex = 0;
                        }
                    }
                }
            }

            return actual;
        }

        public EncodedStreamValidatonResult Validate()
        {
            if (_isValid == null)
                return EncodedStreamValidatonResult.Unknown;
            if (_isValid == true)
                return EncodedStreamValidatonResult.Valid;
            else
                return EncodedStreamValidatonResult.Invalid;
        }

        #endregion // Methods

        #region Helpers

        private void DetectFirstLine()
        {
            NntpStreamReader reader = new NntpStreamReader(BaseStream, NntpClient.DefaultTextEncoding, 1);

            // get the first line but skip empty lines (which can occour at the top
            string line;
            do
            {
                line = reader.ReadLine();
            } while (line.Length == 0);

            if (!line.StartsWith("begin"))
            {
                throw new InvalidOperationException("The underlying stream was not recognized as a valid UUEncoded stream");
            }
            else
            {
                string[] words = line.Split(' ');
                
                if (words.Length < 3)
                {
                    throw new InvalidOperationException("UUEncoded header line had an invalid amount of arguments");
                }
                else
                {
                    FileSecurity = int.Parse(words[1]);
                    FileName = string.Join(" ", words.Skip(2));

                    _isFirstLineDetected = true;
                }
            }
        }

        #endregion // Helpers

        #region Properties

        public string FileName { get; private set; }
        public int FileSecurity { get; private set; }

        #endregion // Properties
    }
}
